// 什么是函子（Functor）
// 容器：包含值和值的变形关系（这个变形关系就是函数）
// 函子：是一个特殊的容器，通过一个普通的对象来实现，该对象具有map方法，map方法可以运行一个函数对值进行处理（变形关系）

const fp = require('lodash/fp')
const { compose, curry } = require('folktale/core/lambda')
const fs = require('fs')
const { task } = require('folktale/concurrency/task')

class Container {
  static of (value) {
    return new Container(value)
  }

  constructor (value) {
    this._value = value
  }

  map (fn) {
    return Container.of(fn(this._value))
  }
}

console.log(Container.of(5).map(x => x + 1).map(x => x * x))
// 处理空报错
// console.log(Container.of('null').map(x => x.toUpperCase()))

// 函数式编程的运算不直接操作值，而是由函子完成
// 函子就是一个实现来map契约的对象
// 我们可以把函子想象成一个盒子，这个盒子里封装了一个值
// 想要处理盒子中的值，我们需要给盒子的map方法传递一个处理值的函数（纯函数），由这个函数来对值进行处理
// 最终map方法返回一个包含新值的盒子（函子）


/* MayBe函子，对外部空值进行处理 */
class MayBe {
  static of (value) {
    return new MayBe(value)
  }

  constructor (value) {
    this._value = value
  }

  map (fn) {
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
  }

  isNothing (value) {
    return this._value === null || undefined
  }
}

console.log(MayBe.of(null).map(x => x.toUpperCase()))
console.log(MayBe.of('null').map(x => x.toUpperCase()))

/* Either函子类似于if...else...，异常会让函数变得不纯，Either函子可以用来做异常处理 */
class Left {
  static of (value) {
    return new Left(value)
  }

  constructor (value) {
    this._value = value
  }

  map () {
    return this
  }
}

class Right {
  static of (value) {
    return new Right(value)
  }

  constructor (value) {
    this._value = value
  }

  map (fn) {
    return Right.of(fn(this._value))
  }
}

function parseJson (str) {
  try {
    return Right.of(JSON.parse(str))
  } catch (e) {
    return Left.of({ error: e.message })
  }
}

console.log(parseJson('{ name: zs }'))
console.log(parseJson('{ "name": "zs" }'))

/* 
  IO函子
  IO函子中的_value是一个函数，这里把函数作为值来处理
  IO函子可以把不纯的动作存储到_value中，延迟执行这个不纯的操作（惰性执行），包装当前的操作
  把不纯的操作交给调用者来处理
*/
class IO {
  static of (value) {
    return new IO(function () {
      return value
    })
  }

  constructor (fn) {
    this._value = fn
  }

  map (fn) {
    return new IO(fp.flowRight(fn, this._value))
  }
}

console.log((IO.of(process).map(p => p.execPath))._value())
// IO函子存在嵌套函子调用的不方便，._value()._value()
const readFileIO = function (filename) {
  return new IO(function () {
    return fs.readFileSync(filename, 'utf-8')
  })
}
const print = function (x) {
  return new IO(function () {
    console.log(x)
    return x
  })
}

const cat = fp.flowRight(print, readFileIO)
console.log(cat('package.json')._value()._value())
/*
  解决IO函子嵌套问题：Monad函子
  可以变扁的Pointed函子，IO(IO(x))
  一个函子如果具有join和of两个方法并遵守一些定律就是一个Monad
*/
class IOMonad {
  static of (value) {
    return new IOMonad(function () {
      return value
    })
  }

  constructor (fn) {
    this._value = fn
  }

  map (fn) {
    return new IOMonad(fp.flowRight(fn, this._value))
  }

  join () {
    return this._value()
  }

  flatMap (fn) {
    return this.map(fn).join()
  }
}
const readFileIOMonad = function (filename) {
  return new IOMonad(function () {
    return fs.readFileSync(filename, 'utf-8')
  })
}
const printMonad = function (x) {
  return new IOMonad(function () {
    console.log(x)
    return x
  })
}
const catMonad = readFileIOMonad('package.json')
                  .map(x => fp.toUpper(x))
                  .flatMap(printMonad)
                  .join()
console.log('<----------------catMonad---------------->')
console.log(catMonad)


/* Folktale中的compose，curry */
let ff = curry(2, (x, y) => x + y)
console.log(ff(1, 2))
console.log(compose(fp.toUpper, fp.first)(['one', 'two']))

/* Folktale中的task函子，执行异步任务 */
function readFile (filename) {
  return task(resolver => {
    fs.readFile(filename, 'utf-8', (err, data) => {
      if (err) resolver.reject(err)
      resolver.resolve(data)
    })
  })
}

readFile('package.json')
  .map(fp.split('\n'))
  .map(fp.find(x => x.includes('version')))
  .run()
  .listen({
    onRejected: err => {
      console.log(err)
    },
    onResolved: value => {
      console.log(value)
    }
  })

  /* Point函子是实现来of静态方法的函子，of方法是为了避免使用new来创建对象，更深层的含义是of方法用来把值放到上下文Context（把值放到容器中，使用map来处理值），上文的的函子都是Point函子 */